/**********************************************/
/*** Programacao Concorrentes               ***/
/*** Trabalho 1                             ***/
/*** Grupo 03 Turma A                       ***/
/*** Brenno Candido                 7696500 ***/
/*** Jose Victor Uliana Martins     7656620 ***/
/*** Maria Fernanda Garbim Avelino  7277562	***/
/**********************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>

#define FILTER_SIZE 5 // dimesao do filtro
#define FILTER_SIZE_SQUARED FILTER_SIZE*FILTER_SIZE

#define THR_PER_BLOCK 1024

/** variavel de 1 byte que pode conter valores de 0-255 **/
typedef unsigned char uchar;

/*
 * Estrutura que contem os dados de uma image
 * no formato PPM
 * OBS: o header da imagem NAO deve ter COMENTARIO
 */
typedef struct {
	char identifier[3];
	int width, height, colors_range;
	uchar *r, *g, *b;
} Image;

/*
 * Funcao que realiza a leitura de uma imagem em
 * PPM que NAO posssi COMENTARIO
 * Argumentos:
 * 	f_in (input file): descritor do arquivo da imagem PPM a ser lida
 * 	in: estrutura de dados a ser amazenados a imagem lida
 * 	em f_in
 */ 
void readImage(FILE* f_in, Image* in) {
	int i, j;
	
	// Le dos dados do header da image PPM
	fscanf(f_in, " %s %d %d %d", in->identifier, &in->width, &in->height,  &in->colors_range);
	
	// Aloca vetores de cores no padrao RGB
	in->r = (uchar*)malloc(in->width*in->height*sizeof(uchar));
	in->g = (uchar*)malloc(in->width*in->height*sizeof(uchar));
	in->b = (uchar*)malloc(in->width*in->height*sizeof(uchar));
	
	// Le os dados dos bytes de cores RGB
	for(i=0; i<in->height; i++) {
		for(j=0; j<in->width; j++) {
			fscanf(f_in, " %hhu", &in->r[in->width*i+j]);
			fscanf(f_in, " %hhu", &in->g[in->width*i+j]);
			fscanf(f_in, " %hhu", &in->b[in->width*i+j]);
		}
	}
}

/*
 * Funcao que realiza a escrita de uma imagem em PPM
 * Argumentos:
 * 	f_out (output file): descritor de arquivo da imagem PPM a ser escrito
 * 	out: estrutura de dados com os dados da imagem a ser escrita
 * 	em f_out
 */
void writeImage(FILE* f_out, Image *out) {
	int i, j;
	
	// Escreve os dados do header da imagem PPM
	fprintf(f_out, "%s\n%d %d\n%d\n", out->identifier, out->width, out->height, out->colors_range);
	
	// Escreve os dados dos bytes de cores RGB
	for(i=0; i<out->height; i++) {
		for(j=0; j<out->width; j++) {
			fprintf(f_out, "%hhu ", out->r[out->width*i+j]);
			fprintf(f_out, "%hhu ", out->g[out->width*i+j]);
			fprintf(f_out, "%hhu ", out->b[out->width*i+j]);
		}
		fprintf(f_out, "\n");
	}
}

/*
 * Desaloca memoria usada pela estrutura da imagem PPM
 * Argumentos:
 * 	img (image): ponteiro da imagem a ser desalocada
 */
void clear(Image *img) {
	img->identifier[0] = '\0';
	img->width = 0;
	img->height = 0;
	img->colors_range = 0;
	free(img->r);
	free(img->g);
	free(img->b);
	img->r = NULL;
	img->g = NULL;
	img->b = NULL;
}

/*
 * Copia o cabecalho da imagem b para a imagem, a = b
 * Argumentos:
 * 	a (imagem): imagem alvo
 * 	b (imagem): imagem fonte
 */
void cpyHeader(Image *a, Image *b) {
	strcpy(a->identifier, b->identifier);
	a->width = b->width;
	a->height = b->height;
	a->colors_range = b->colors_range;
	
	// Aloca vetores de cores no padrao RGB
	a->r = (uchar*)malloc(b->width*b->height*sizeof(uchar));
	a->g = (uchar*)malloc(b->width*b->height*sizeof(uchar));
	a->b = (uchar*)malloc(b->width*b->height*sizeof(uchar));
}

/*
 * 
 * Passa a mascara smooth em 1 pixel
 * Argumentos:
 * 	px (pixels): vetor de pixels
 * 	i: indice de linha da posicao do pixel
 * 	j: indice de coluna do pixel
 * 	width (largura): largura da imagem para pode ser realizado
 * 	o acesso na posicao do pixel (i,j) correto do vetor
 * Retorno:
 * 	Novo valor do pixel (i, j) da imagem
 * OBS: Funcao do tipo DEVICE
 */
__device__ uchar smooth(uchar* px, int i, int j, int width, int height){
	int aux_i, aux_j, sum;
	int filter_radius = FILTER_SIZE/2;
	
	sum = 0;
	for(aux_i = i-filter_radius; aux_i<=i+filter_radius; aux_i++) {
		for(aux_j = j-filter_radius; aux_j<=j+filter_radius; aux_j++) {
			if(aux_i >= 0 && aux_j >= 0 && aux_i < height && aux_j < width) {
				sum += px[aux_i*width+aux_j];
			}
			else
				sum += 0;
		}
	}
	return sum/(FILTER_SIZE*FILTER_SIZE);
}
/*
 * Funcao KERNEL
 * Essa funcao e responsavel determina o pixel que ela
 * e responsavel por chamar a funcao smooth() (passar a
 * mascara).
 * Argumentos:
 * 	in: vetor de entrada com os pixels referente a uma cor (R||G||B)
 * 	out: vetor de saida com os pixels referente a uma cor (R||G||B)
 * 	width: largura da image
 * 	height: altura da image
 */
__global__ void smoothFilter(uchar *in, uchar *out, int width, int height) {
	int i, j;
	
	/*
	 * Calcula indice unico referente ao pixel que a thread esta responsavel
	 */
	i = blockIdx.y;
	j = blockIdx.x*blockDim.x + threadIdx.x;

	// chama a funcao para passar a mascara no pixel
	out[i*width+j] = smooth(in, i, j, width, height);
}

int main(int argc, char* argv[]) {
	FILE* f_in, *f_out;
	Image h_in, h_out;
	uchar *d_in, *d_out;
	int size;
	
	// Abre arquivo passado como primeiro argumento de execucao (argv[1])
	f_in = fopen(argv[1], "r");
	if(!f_in)
		fprintf(stderr, "Can not read a file \"%s\". Please, check that the file exists\n", argv[1]);
	
	// Le imagem do arquivo
	readImage(f_in, &h_in);
	fclose(f_in);
	
	// Copia o header da imagem lida para a novo imagem que sera gerada como saida
	cpyHeader(&h_out, &h_in);
	
	// Aloca vetores para o device
	size = h_in.width*h_in.height*sizeof(uchar);
	cudaMalloc((void**)&d_in, size);
	cudaMalloc((void**)&d_out, size);
	
	// Calcula numero de blocos necessario em cada dimencao
	int blocks_x = (int)ceil((double)h_in.width/(double)THR_PER_BLOCK);
	int blocks_y = h_in.height;

	// define numero de blocos e threads por blocos para passar na funao kernel
	dim3 numBlocks(blocks_x, blocks_y);
	dim3 thr_per_block(THR_PER_BLOCK);
	
	/*
	 * Para obter uma medida de tempo mais acurada para realizar
	 * a analise do algoritmo sequencial do smoothFilter(), para
	 * isso utilizamos as variaveis do tipo clock_t
	 * t1: tempo inicial
	 * t2: tempo final
	 * dt: tempo levado para executar a funcao smoothFilter()
	 */
	clock_t t1, t2, dt;
	t1 = clock();  // tempo inicial
	
	// Passa o filtro no vetor de cor R
	cudaMemcpy(d_in, h_in.r, size,  cudaMemcpyHostToDevice);
	smoothFilter<<<numBlocks, thr_per_block>>>(d_in, d_out, h_in.width, h_in.height);
	cudaMemcpy(h_out.r, d_out, size, cudaMemcpyDeviceToHost);
	
	// Passa o filtro no vetor de cor G
	cudaMemcpy(d_in, h_in.g, size,  cudaMemcpyHostToDevice);
	smoothFilter<<<numBlocks, thr_per_block>>>(d_in, d_out, h_in.width, h_in.height);
	cudaMemcpy(h_out.g, d_out, size, cudaMemcpyDeviceToHost);
	
	// Passa o filtro no vetor de cor B
	cudaMemcpy(d_in, h_in.b, size,  cudaMemcpyHostToDevice);
	smoothFilter<<<numBlocks, thr_per_block>>>(d_in, d_out, h_in.width, h_in.height);
	cudaMemcpy(h_out.b, d_out, size, cudaMemcpyDeviceToHost);
	
	t2 = clock(); // tempo final
	dt = t2-t1;
	printf("%.5lf ", (double)dt/CLOCKS_PER_SEC);

	// Abre arquivo para escrever a image out
	f_out = fopen("out.ppm", "w");
	if(!f_out)
		fprintf(stderr, "Can not write a file \"out.ppm\"\n");
	
	// Escreve a image out em f_out
	writeImage(f_out, &h_out);
	fclose(f_out);
	
	// Limpa memoria
	clear(&h_in);
	cudaFree(d_in);
	
	clear(&h_out);
	cudaFree(d_out);
	return 0;
}
